/* pur @ 进程监控
 * date @ 2014.09.02
 * author @ wanghaibin
 * 说明：
 *	1、监控当前用户下的进程
 *	2、当监控进程退出时会给子进程发送SIGINT信号，等待5s后如果子进程未退出会执行kill -9命令
 *	3、如果停止进程没有传递 -u 参数则会按照停止当前用户下的监控进程
 *	4、假定命令行输出不会超过1024个字节的长度
 */
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "parsingargs.h"
#include "singleconfig.h"

const char *CURRENT_VERSION = "0.1";
const char *CHECK_INTERNAL = "INTERNAL";
const char *PROC_LIST = "PROC_LIST";
const char *KEY_START = "start"; //操作符启动
const char *KEY_STOP = "stop";   //操作符停止
bool g_bClose = false;           //是否要退出所有程序
std::string g_moduleName;        //配置文件名

enum OPType {
    OP_NONE = 0,
    OP_START, //启动进程
    OP_STOP   //停止进程
};

/* pur @ 存放参数列表的信息
 */
struct ArgInfo {
    std::string m_configPath; //配置文件路径
    bool m_bDaemon;           //是否为守护进程
    OPType m_operation;       //当前操作类型
    ArgInfo()
    {
        m_bDaemon = false;
        m_operation = OP_NONE;
    }
};

/* pur @ 存放要监控的进程信息
 */
struct ProcInfo {
    std::string m_name;    //进程的别名，对应配置文件中=左边的名字
    std::string m_command; //进程的命令行内容
    int m_pid;
    ProcInfo()
    {
        m_pid = -1;
    }
};

/* pur @ 接收到退出信号
 * para @ sigNo 信号值
 * return @
 */
void ReceiveQuit(int sigNo)
{
    g_bClose = true;
    printf("receive kill -2 \n");
}

/* pur @ 帮助信息
 * para @ helpInfo 要打印的参数信息
 */
void usage(const char *helpInfo)
{
    printf("Usage: ./test [option]\n");
    printf("Options:\n");
    printf("%s\n", helpInfo);
}
/* pur @ 解析命令行参数
 * para @ argc 个数
 * para @ argv 参数列表
 * return @ 0 解析成功，<0需要直接退出程序
 */
void parseArgv(int argc, char **argv, ArgInfo &arginfo)
{
    ParsingArgs pa;
    pa.AddArgType("f", "conFile", ParsingArgs::MUST_VALUE, "configure file path");
    pa.AddArgType("d", "daemon", ParsingArgs::NO_VALUE, "as a daemon progress");
    pa.AddArgType("o", "operation", ParsingArgs::MUST_VALUE,
                  "curent operation,now is start or stop");
    pa.AddArgType("h", "help", ParsingArgs::NO_VALUE, "print help information");
    pa.AddArgType("v", "version", ParsingArgs::NO_VALUE, "show current version");
    pa.SetUniqArgs("help:version");    //只能唯一出现h和v
    pa.SetMutexArgs("help:version:f"); // h、v、f互斥
    pa.SetDependence("f", "o");        // f后必须有o

    std::map<std::string, std::vector<std::string>> result;
    std::string errPos;
    int iRet = pa.Parse(argc, argv, result, errPos);

    if (0 != iRet) {
        if (-6 != iRet) {
            printf("parse arguments failed please check it! pos=%d, %s\n", iRet, errPos.c_str());
        }
        usage(pa.GetHelpInfo());
        exit(-1);
    }
    std::map<std::string, std::vector<std::string>>::iterator it = result.find("h");
    if (it != result.end()) {
        usage(pa.GetHelpInfo());
        exit(-1);
    }
    it = result.find("v");
    if (it != result.end()) {
        printf("current version = %s\n", CURRENT_VERSION);
        exit(0);
    }
    it = result.find("f");
    if (it != result.end() && it->second.size() > 0) {
        arginfo.m_configPath = it->second.at(0);
    }

    it = result.find("o");
    if (it != result.end() && it->second.size() > 0) {
        std::string strValue = it->second.at(0);
        if (0 == strValue.compare(KEY_START)) {
            arginfo.m_operation = OP_START;
        } else if (0 == strValue.compare(KEY_STOP)) {
            arginfo.m_operation = OP_STOP;
        } else {
            printf("operation error of %s\n", strValue.c_str());
            exit(0);
        }
    }
    it = result.find("d");
    if (it != result.end()) {
        arginfo.m_bDaemon = true;
    }
}

/* pur @ 字符串分隔函数
 *  * para @ str 要分隔的字符串
 *   * para @ pattern 分隔符，不能按照多个分隔符分隔
 *    * return @ 返回分隔后的字符串列表，顺序按照str的先后顺序存放
 *    */
void Split(const std::string &str, const std::string &pattern, std::vector<std::string> &result)
{
    std::string::size_type pos;
    std::string tmp = str;
    tmp += pattern; //扩展字符串以方便操作
    size_t size = tmp.size();
    for (size_t i = 0; i < size; i++) {
        pos = tmp.find(pattern, i);
        if (pos < size) {
            std::string s = tmp.substr(i, pos - i);
            if (!s.empty()) {
                result.push_back(s);
            }
            i = pos + pattern.size() - 1;
        }
    }
}

/* pur @ 获取所有进程的信息
 * para @ procList 存放获取到的进程信息
 * return @
 */
void GetAllProcInfo(std::vector<ProcInfo *> &procList)
{
    std::string strAll = SingleConfig::GetStrValue(g_moduleName.c_str(), PROC_LIST);
    std::vector<std::string> vecAll;
    Split(strAll, ",", vecAll);
    for (size_t i = 0; i < vecAll.size(); ++i) {
        strAll = SingleConfig::GetStrValue(g_moduleName.c_str(), vecAll[i].c_str());
        if (!strAll.empty()) {
            ProcInfo *info = NULL;
            info = new ProcInfo();
            if (NULL != info) {
                info->m_name = vecAll[i];
                info->m_command = strAll;
                procList.push_back(info);
            }
        }
    }
}

/* pur @ 销毁申请的空间
 */
void DeleteProcInfo(std::vector<ProcInfo *> &procList)
{
    for (size_t i = 0; i < procList.size(); ++i) {
        ProcInfo *tmp = procList[i];
        if (NULL != tmp) {
            delete tmp;
            tmp = NULL;
        }
    }
    procList.clear();
}

/* pur @ 让当前进程成为守护进程
 * return @
 */
void MakeDaemon()
{
    pid_t pid;
    if (0 < (pid = fork())) {
        exit(0); //是父进程，结束父进程
    } else if (pid < 0) {
        exit(1); // fork失败，退出
    }

    // 是第一子进程，后台继续执行
    setsid(); //第一子进程成为新的会话组长和进程组长
    //与控制终端分离
    if (0 < (pid = fork())) {
        exit(0); //是第一子进程，结束第一子进程
    } else if (pid < 0) {
        exit(1); // fork失败，退出
    }
}

/* pur @ 启动子进程
 * para @ pid //子进程id
 * para @ command 要启动的进程的全路径和参数列表串
 * return @
 */
void StartProc(int *pid, const char *command)
{
    char *argv[50] = { 0 };
    std::vector<std::string> vecComm;
    Split(command, " ", vecComm);
    for (size_t i = 0; i < vecComm.size(); ++i) {
        argv[i] = strdup(vecComm[i].c_str());
    }

    int iPid = -1;
    iPid = fork();
    if (iPid == 0) { //子进程
        *pid = getpid();
        if (-1 == execvp(argv[0], argv)) {
            printf("start proc %s failed, err->%s\n", argv[0], strerror(errno));
            exit(0);
        } else {
            printf("start proc %s succeed\n", argv[0]);
        }

    } else if (iPid > 0) { //父进程
        *pid = iPid;
    }

    for (size_t j = 0; j < vecComm.size(); ++j) {
        if (NULL != argv[j]) {
            free(argv[j]);
            argv[j] = NULL;
        }
    }
}

int StartAll(std::vector<ProcInfo *> &procList)
{
    for (size_t i = 0; i < procList.size(); ++i) {
        if (!procList[i]->m_command.empty()) {
            printf("to start proc of =%s\n", procList[i]->m_command.c_str());
            StartProc(&(procList[i]->m_pid), procList[i]->m_command.c_str());
        }
    }
    return 0;
}

int StopAll(std::vector<ProcInfo *> &procList)
{
    for (size_t i = 0; i < procList.size(); ++i) {
        if (NULL != procList[i] && procList[i]->m_pid > 0) {
            kill(procList[i]->m_pid, SIGINT); //发送中断信号
        }
    }
    sleep(5); //等待5s让子进程退出

    for (size_t i = 0; i < procList.size(); ++i) {
        if (NULL != procList[i] && procList[i]->m_pid > 0 && -1 == kill(procList[i]->m_pid, 0)) {
            char tmp[30] = { 0 };
            sprintf(tmp, "kill -9 %d", procList[i]->m_pid);
            system(tmp); //调用系统kill -9强制杀死
        }
    }

    return 0;
}

/* pur @ 监测进程是否退出，如果退出则重启进程
 * para @ procList 要监控的进程列表
 */
void CheckProcAndStart(std::vector<ProcInfo *> &procList)
{
    for (size_t i = 0; i < procList.size(); ++i) {
        if (NULL != procList[i] && procList[i]->m_pid > 0) {
            int iRet = 0;
            iRet = kill(procList[i]->m_pid, 0);            //监测进程状态
            if (-1 == iRet && !g_bClose && ESRCH == errno) //当pid不存在的时候再重启
            {
                StartProc(&(procList[i]->m_pid), procList[i]->m_command.c_str());
            }
        }
    }
}

void ExecShell(const char *cmd, std::vector<std::string> &result)
{
    FILE *fd = NULL;
    if ((fd = popen(cmd, "r")) != NULL) {
        char tmp[1024] = { 0 };              //假定一行的内容不超过1024个字符
        while (fgets(tmp, 1024, fd) != NULL) // judge end of file
        {                                    //读取一行,
            result.push_back(std::string(tmp));
            memset(tmp, 0, 1024);
        }
        pclose(fd);
    }
}

/* pur @ 监测当前进程是否启动
 * para @ operation 过滤的关键字（操作）
 * para @ procName 过滤的关键字（进程名）
 * para @ user 过滤关键字（用户，默认为当前用户）
 * return @ 返回除当前进程外的所有符合条件的进程id
 */
std::vector<int> CheckSelf(const char *operation, const char *procName, const char *user = NULL)
{
    std::vector<int> vecPid;
    std::string strUser;
    if (NULL != user) {
        strUser = user;
    }
    std::vector<std::string> cmdResu;
    if (strUser.empty()) {
        ExecShell("whoami", cmdResu);
        if (!cmdResu.empty()) {
            strUser = cmdResu[0];
        }
    }
    std::string cmdInfo("ps -eo pid,user,cmd");
    cmdInfo.append("|grep -w \"").append(procName).append("\"");
    cmdInfo.append("|grep -w \"").append(operation).append("\"");
    if (!strUser.empty())
        cmdInfo.append("|grep -w \"").append(operation).append("\"");
    cmdInfo.append("|grep -v \"grep\"");
    cmdResu.clear();
    ExecShell(cmdInfo.c_str(), cmdResu);
    std::vector<std::string> vecProc;
    int curPid = getpid();
    for (size_t i = 0; i < cmdResu.size(); ++i) {
        vecProc.clear();
        Split(cmdResu[i].c_str(), " ", vecProc);
        if (vecProc.size() > 0 && curPid != atoi(vecProc[0].c_str())) {
            vecPid.push_back(atoi(vecProc[0].c_str()));
        }
    }
    return vecPid;
}

void StopMonitor(int pid)
{
    kill(pid, SIGINT);
}

int main(int argc, const char *argv[])
{
    signal(SIGINT, &ReceiveQuit);  //收到中断，kill -2 或ctrl+c
    signal(SIGQUIT, &ReceiveQuit); //收到Quit
    signal(SIGTERM, &ReceiveQuit); //直接kill
    signal(SIGKILL, &ReceiveQuit);
    signal(SIGCHLD, SIG_IGN); //忽略子进程退出
    ArgInfo arginfo;
    parseArgv(argc, (char **)argv, arginfo);
    if (arginfo.m_bDaemon) {
        MakeDaemon();
    }

    std::string errInfo;
    if (!SingleConfig::SetConfigPath(arginfo.m_configPath.c_str(), errInfo)) {
        printf("read configure file err=%s\n", errInfo.c_str());
        return -1;
    }
    g_moduleName = SingleConfig::GetFileName(arginfo.m_configPath.c_str());
    std::string strProcName = SingleConfig::GetFileName(argv[0]);
    SingleConfig::SetDefaultValue(g_moduleName.c_str(), CHECK_INTERNAL, 5);
    std::vector<ProcInfo *> procList; //所有进程的列表
    GetAllProcInfo(procList);

    switch (arginfo.m_operation) {
        case OP_START: {
            std::vector<int> vecInfo = CheckSelf(KEY_START, strProcName.c_str());
            if (vecInfo.empty()) {
                StartAll(procList);
            } else {
                printf("monitor start had been started!\n");
                g_bClose = true;
            }
        } break;
        case OP_STOP: {
            g_bClose = true;
            std::vector<int> vecInfo = CheckSelf(KEY_START, strProcName.c_str());
            if (!vecInfo.empty()) {
                for (size_t i = 0; i < vecInfo.size(); ++i) {
                    printf("to stop proc of %d\n", vecInfo[i]);
                    StopMonitor(vecInfo[i]);
                }
            } else {
                printf("monitor stop had been started!\n");
            }
        } break;
        default:
            break;
    }

    ///循环监控进程是否退出
    int iCheckInter = SingleConfig::GetIntValue(g_moduleName.c_str(), CHECK_INTERNAL);
    while (true) {
        if (g_bClose) {
            StopAll(procList);
            break;
        }
        CheckProcAndStart(procList);
        sleep(iCheckInter);
    }
    DeleteProcInfo(procList);
    return 0;
}
